import xml2json from './xml2json'

const EVAL_SAFE = (domain, globals_dict = {}) => {
  // console.log('EVAL_SAFE ', domain, globals_dict)
  // const to_replaced = { '\\(': '[', '\\)': ']', False: 'false', True: 'true' }

  let domain2 = domain

  const kwargs = { ...globals_dict }

  const fn_str = []
  fn_str.push('() => {')
  Object.keys(kwargs).forEach(item => {
    const vals = kwargs[item]
    // const is_str = typeof vals === 'string'
    // const is_arr = Array.isArray(vals)
    // const vals2 = is_str ? `'${vals}'` : is_arr ? `[${vals}]` : vals
    const vals2 = JSON.stringify(vals)
    // console.log('fn eval 1:', item, vals, vals2)
    const item2 = item === 'function' ? 'function2' : item
    const str_to_push = `const ${item2} = ${vals2}`
    // console.log('fn eval 1:', item, vals, vals2, str_to_push)
    fn_str.push(str_to_push)
  })

  fn_str.push(`return ${domain2}`)
  fn_str.push('}')

  const fn_str2 = fn_str.join('\n')
  // console.log('fn eval 222:', parent)

  try {
    const fn = eval(fn_str2)
    // console.log('fn eval fn::', fn)
    const ret = fn()
    // console.log(ret)

    return ret
  } catch (e) {
    // console.log(e)
    console.log('fn eval:', domain)

    throw new Error(e)
    // return undefined
  }
}

const QWeb = {
  templates: {},
  prefix: 't',
  reg: new RegExp(),
  tag: {},
  att: {},
  ValueException: function (value, message) {
    this.value = value
    this.message = message
  },
  eval_object: function (e, v) {
    // TODO: Currently this will also replace and, or, ... in strings. Try
    // 'hi boys and girls' != '' and 1 == 1  -- will be replaced to : 'hi boys && girls' != '' && 1 == 1
    // try to find a solution without tokenizing
    e = '(' + e + ')'
    e = e.replace(/\band\b/g, ' && ')
    e = e.replace(/\bor\b/g, ' || ')
    e = e.replace(/\bgt\b/g, ' > ')
    e = e.replace(/\bgte\b/g, ' >= ')
    e = e.replace(/\blt\b/g, ' < ')
    e = e.replace(/\blte\b/g, ' <= ')
    if (v[e] != undefined) {
      return v[e]
    } else {
      return EVAL_SAFE(e, v)

      //   with (v) return eval(e)
    }
  },
  eval_str: function (e, v) {
    var r = this.eval_object(e, v)
    r = typeof r == 'undefined' || r == null ? '' : r.toString()
    return e == '0' ? v['0'] : r
  },
  eval_format: function (e, v) {
    var m,
      src = e.split(/#/),
      r = src[0]
    for (var i = 1; i < src.length; i++) {
      if ((m = src[i].match(/^{(.*)}(.*)/))) {
        r += this.eval_str(m[1], v) + m[2]
      } else {
        r += '#' + src[i]
      }
    }
    return r
  },
  eval_bool: function (e, v) {
    return !!this.eval_object(e, v)
  },
  trim: function (v, mode) {
    if (!v || !mode) return v
    switch (mode) {
      case 'both':
        return v.replace(/^\s*|\s*$/g, '')
      case 'left':
        return v.replace(/^\s*/, '')
      case 'right':
        return v.replace(/\s*$/, '')
    }
    throw new QWeb.ValueException(
      mode,
      "unknown trimming mode, trim mode must follow the pattern '[inner] (left|right|both)'"
    )
  },
  escape_text: function (s) {
    return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
  },
  escape_att: function (s) {
    return s
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
  },
  render_node: function (e, v, inner_trim) {
    if (e.nodeType == 3) {
      return inner_trim ? this.trim(e.data, inner_trim) : e.data
    }
    if (e.nodeType == 1) {
      var g_att = {}
      var t_att = {}
      var t_render = null
      var a = e.attributes
      for (var i = 0; i < a.length; i++) {
        var an = a[i].name,
          av = a[i].value
        var m
        if ((m = an.match(this.reg))) {
          var n = m[1]
          if (n == 'eval') {
            n = m[2].substring(1)
            av = this.eval_str(av, v)
          }
          var f
          if ((f = this.att[n])) {
            this[f](e, t_att, g_att, v, m[2], av)
          } else if ((f = this.tag[n])) {
            t_render = f
          }
          t_att[n] = av
        } else {
          g_att[an] = av
        }
      }
      if (inner_trim && !t_att['trim']) {
        t_att['trim'] = 'inner ' + inner_trim
      }
      if (t_render) {
        return this[t_render](e, t_att, g_att, v)
      }
      return this.render_element(e, t_att, g_att, v)
    }
    return ''
  },
  render_element: function (e, t_att, g_att, v) {
    var inner = '',
      ec = e.childNodes,
      trim = t_att['trim'],
      inner_trim
    if (trim) {
      if (/\binner\b/.test(trim)) {
        inner_trim = true
        if (trim == 'inner') {
          trim = 'both'
        }
      }
      var tm = /\b(both|left|right)\b/.exec(trim)
      if (tm) trim = tm[1]
    }
    for (var i = 0; i < ec.length; i++) {
      inner += inner_trim
        ? this.trim(this.render_node(ec[i], v, inner_trim ? trim : null), trim)
        : this.render_node(ec[i], v, inner_trim ? trim : null)
    }
    if (trim && !inner_trim) {
      inner = this.trim(inner, trim)
    }
    if (e.tagName == this.prefix) {
      return inner
    }
    var att = ''
    for (var an in g_att) {
      att += ' ' + an + '="' + this.escape_att(g_att[an]) + '"'
    }
    // Some IE versions have problems with closed tags
    var opentag = !!t_att['opentag'] && this.eval_bool(t_att['opentag'], v)
    return inner.length || opentag
      ? '<' + e.tagName + att + '>' + inner + '</' + e.tagName + '>'
      : '<' + e.tagName + att + '/>'
  },
  render_att_att: function (e, t_att, g_att, v, ext, av) {
    if (ext) {
      var attv = this.eval_object(av, v)
      if (attv != null) {
        g_att[ext.substring(1)] = attv.toString()
      }
    } else {
      var o = this.eval_object(av, v)
      if (o != null) {
        // TODO: http://bonsaiden.github.com/JavaScript-Garden/#types.typeof
        if (o.constructor == Array && o.length > 1 && o[1] != null) {
          g_att[o[0]] = new String(o[1])
        } else if (o.constructor == Object) {
          for (var i in o) {
            if (o[i] != null) {
              g_att[i] = new String(o[i])
            }
          }
        }
      }
    }
  },
  render_att_attf: function (e, t_att, g_att, v, ext, av) {
    g_att[ext.substring(1)] = this.eval_format(av, v)
  },
  render_tag_raw: function (e, t_att, g_att, v) {
    return this.eval_str(t_att['raw'], v)
  },
  render_tag_rawf: function (e, t_att, g_att, v) {
    return this.eval_format(t_att['rawf'], v)
  },
  /*
   * Idea: if the name of the tag != t render the tag around the value <a name="a" t-esc="label"/>
   */
  render_tag_esc: function (e, t_att, g_att, v) {
    return this.escape_text(this.eval_str(t_att['esc'], v))
  },
  render_tag_escf: function (e, t_att, g_att, v) {
    return this.escape_text(this.eval_format(t_att['escf'], v))
  },
  render_tag_if: function (e, t_att, g_att, v) {
    return this.eval_bool(t_att['if'], v)
      ? this.render_element(e, t_att, g_att, v)
      : ''
  },
  render_tag_set: function (e, t_att, g_att, v) {
    var ev = t_att['value']
    if (ev && ev.constructor != Function) {
      v[t_att['set']] = this.eval_object(ev, v)
    } else {
      v[t_att['set']] = this.render_element(e, t_att, g_att, v)
    }
    return ''
  },
  render_tag_call: function (e, t_att, g_att, v) {
    var d = v
    if (!t_att['import']) {
      d = {}
      for (var i in v) {
        d[i] = v[i]
      }
    }
    d['0'] = this.render_element(e, t_att, g_att, d)
    return this.render(t_att['call'], d)
  },
  render_tag_js: function (e, t_att, g_att, v) {
    var dict_name = t_att['js'] || 'dict'
    v[dict_name] = v
    var r = this.eval_str(this.render_element(e, t_att, g_att, v), v)
    delete v[dict_name]
    return r || ''
  },
  /**
   * Renders a foreach loop (@t-foreach).
   *
   * Adds the following elements to its context, where <code>${name}</code>
   * is specified via <code>@t-as</code>:
   * * <code>${name}</code> The current element itself
   * * <code>${name}_value</code> Same as <code>${name}</code>
   * * <code>${name}_index</code> The 0-based index of the current element
   * * <code>${name}_first</code> Whether the current element is the first one
   * * <code>${name}_parity</code> odd|even (as strings)
   * * <code>${name}_all</code> The iterated collection itself
   *
   * If the collection being iterated is an array, also adds:
   * * <code>${name}_last</code> Whether the current element is the last one
   * * All members of the current object
   *
   * If the collection being iterated is an object, the value is actually the object's key
   *
   * @param e ?
   * @param t_att attributes of the element being <code>t-foreach</code>'d
   * @param g_att ?
   * @param old_context the context in which the foreach is evaluated
   */
  render_tag_foreach: function (e, t_att, g_att, old_context) {
    var expr = t_att['foreach']
    var enu = this.eval_object(expr, old_context)
    var ru = []
    if (enu) {
      var val = t_att['as'] || expr.replace(/[^a-zA-Z0-9]/g, '_')
      var context = {}
      for (var i in old_context) {
        context[i] = old_context[i]
      }
      context[val + '_all'] = enu
      var val_value = val + '_value',
        val_index = val + '_index',
        val_first = val + '_first',
        val_last = val + '_last',
        val_parity = val + '_parity'
      var size = enu.length
      if (size) {
        context[val + '_size'] = size
        for (var j = 0; j < size; j++) {
          var cur = enu[j]
          context[val_value] = cur
          context[val_index] = j
          context[val_first] = j == 0
          context[val_last] = j + 1 == size
          context[val_parity] = j % 2 == 1 ? 'odd' : 'even'
          if (cur.constructor == Object) {
            for (var k in cur) {
              context[k] = cur[k]
            }
          }
          context[val] = cur
          var r = this.render_element(e, t_att, g_att, context)
          ru.push(r)
        }
      } else {
        var index = 0
        for (cur in enu) {
          context[val_value] = cur
          context[val_index] = index
          context[val_first] = index == 0
          context[val_parity] = index % 2 == 1 ? 'odd' : 'even'
          context[val] = cur
          ru.push(this.render_element(e, t_att, g_att, context))
          index += 1
        }
      }
      return ru.join('')
    } else {
      return 'qweb: foreach ' + expr + ' not found.'
    }
  },
  hash: function () {
    var l = [],
      m
    for (var i in this) {
      if ((m = i.match(/render_tag_(.*)/))) {
        this.tag[m[1]] = i
        l.push(m[1])
      } else if ((m = i.match(/render_att_(.*)/))) {
        this.att[m[1]] = i
        l.push(m[1])
      }
    }
    l.sort(function (a, b) {
      return a.length > b.length ? -1 : 1
    })
    var s = '^' + this.prefix + '-(eval|' + l.join('|') + '|.*)(.*)$'
    this.reg = new RegExp(s)
  },
  /**
   * returns the correct XMLHttpRequest instance for the browser, or null if
   * it was not able to build any XHR instance.
   *
   * @returns XMLHttpRequest|MSXML2.XMLHTTP.3.0|null
   */
  get_xhr: function () {
    if (window.XMLHttpRequest) {
      return new window.XMLHttpRequest()
    }
    try {
      return new window.ActiveXObject('MSXML2.XMLHTTP.3.0')
    } catch (e) {
      return null
    }
  },

  load_xml2: function (s) {
    var xml
    if (s[0] == '<') {
      /*
             manque ca pour sarrisa
             if(window.DOMParser){
             mozilla
             if(!window.DOMParser){
             var doc = Sarissa.getDomDocument();
             doc.loadXML(sXml);
             return doc;
             };
             };
             */
    } else {
      var req = this.get_xhr()
      if (req) {
        req.open('GET', s, false)
        req.send(null)
        //if ie r.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
        xml = req.responseXML
        /*
                 TODO
                 if intsernetexploror
                 getdomimplmentation() for try catch
                 responseXML.getImplet
                 d=domimple()
                 d.preserverWhitespace=1
                 d.loadXML()

                 xml.preserverWhitespace=1
                 xml.loadXML(r.reponseText)
                 */
        return xml
      }
    }
  },

  load_xml: function (s) {
    return xml2json.getXmlObject(s)
  },

  add_template: function (e) {
    // TODO: keep sources so we can implement reload()
    this.hash()
    if (e.constructor == String) {
      e = this.load_xml(e)
    }

    var ec = e.documentElement
      ? e.documentElement.childNodes
      : e.childNodes
      ? e.childNodes
      : []

    for (var i = 0; i < ec.length; i++) {
      var n = ec[i]
      if (n.nodeType == 1) {
        var name = n.getAttribute(this.prefix + '-name')
        this.templates[name] = n
      }
    }
  },
  render: function (name, v) {
    var e
    if ((e = this.templates[name])) {
      return this.render_node(e, v)
    }
    return 'template ' + name + ' not found'
  }
}

export default QWeb
